/** @file    GardenerBundle.cpp
 *  @author  Alessandro Polo
 *  @version $Id: GardenerBundle.cpp 2893 2010-08-12 02:26:01Z alex $
 *  @brief
 * File containing methods for the wosh::services::GardenerBundle class.
 * The header for this class can be found in GardenerBundle.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "GardenerBundle.h"
 #include "GardenerProgram.h"
 #include <core/ObjectAllocator.h>
 #include <core/Fact.h>
 #include <core/Table.h>
 #include <core/MessageProcessingCommon.h>
 #include <core/FileSystem.h>
 #include <core/SystemError.h>
 #include <core/WoshKernel.h>
 #include <core/PersistenceManager.h>


using namespace std;
namespace wosh {
 namespace services {

 WOSH_REGISTER(wosh::services::GardenerBundle, "wosh::interfaces::services::Gardener", _Gardener_VERSION, _static_GardenerBundle1 )
 WOSH_REGISTER(wosh::services::GardenerBundle, "wosh::BundleGeneric", _Gardener_VERSION, _static_GardenerBundle2 )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

GardenerBundle::GardenerBundle()
	: BundleGeneric() {
	BundleGeneric::setName( _Gardener_NAME, false );
	Properties.update( _KEY_Version, _Gardener_VERSION );
	Requirements.add<wosh::xtended::Scheduler>();

Log.setLevel(LOG_VERBOSE);

	Log(LOG_DEBUG, " Setting up Scheduler.." );
	this->scheduler = ObjectFactory::createTypeOf<wosh::xtended::Scheduler>();
	if ( this->scheduler != NULL ) {
		this->scheduler->setListener(this);
		this->scheduler->setOwnerObject(this);
	 }

	Log(LOG_DEBUG, " Setting default properties and permissions.." );
	Properties.set( _Gardener_KEY_DB,	"$DATABASE/gardener.programs.xml", Permission::RW_RW_R__() );
	Properties.set( _Gardener_KEY_AutoStartProgram,	"", Permission(Permission::Read) );
	Properties.monitor(_Gardener_KEY_AutoStartProgram);

	Log(LOG_DEBUG, " Registering methods.." );
	MethodList* mmListPrograms = Methods.create<MethodList>( _METHOD_List, "List Programs", Permission(Permission::RX) );
	mmListPrograms->setDataFieldExtractor( DataFieldExtractor<GardenerProgram>::createFor< DataAdapterMapT<std::string,GardenerProgram> >(this->programs, "Gardener Programs") );

	MethodRequest* mmCtrlProgram = Methods.create<MethodRequest>( _Gardener_METHOD_control_program, "control program", Permission::R_XR_XR__() );
	mmCtrlProgram->setMethod( this, (MethodRequestPtr)&GardenerBundle::mmDoCtrlProgram );

	setBundleState(Bundle::STATE_CREATED, false);
 }


GardenerBundle::~GardenerBundle() {
	Log(LOG_INFO, " Destroying.." );
	if ( isBundleRunning() ) {
		Log(LOG_WARNING, "~GardenerBundle() : Destroying while Bundle is running! Trying to stop.." );
		bundleStop();
	 }
	Log(LOG_DEBUG, ":~GardenerBundle() : Destroying Programs.." );
	this->programs.clear();
	Log(LOG_DEBUG, ":~GardenerBundle() : Destroying Scheduler.." );
	WRESULT destroyed = ObjectFactory::destroy(this->scheduler);
	this->scheduler = NULL;
	if ( WFAILED(destroyed) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED CreatingScheduler [%d]", destroyed );
	 }

	Log(LOG_VERBOSE, ":~GardenerBundle() : Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL

WRESULT GardenerBundle::bundleStart() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STARTING) ) return WRET_ERR_WRONG_STATE;

	BusBuilding.setMessageHandler(this);
	WRESULT bus_b_connected = BusBuilding.connect( _Bus_Building, Bus::ACTION_DEFERRED | Bus::BUS_CREATE_IF_MISSING );
	if ( WFAILED(bus_b_connected) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Connecting Bus (wosh.Bus.Building)..", bus_b_connected );

	loadPrograms();
	WRESULT ret = WRET_OK;

	ret += this->scheduler->setThreadPoolCount(0);
	ret += this->scheduler->startScheduler(30000);

	//Variant startProg = Properties.getValueOf(_Gardener_KEY_AutoStartProgram);
	//if ( startProg.isStringNotEmpty() ) {
	//	ret = controlProgram(startProg.asString(), true);
	// }

	BundleGeneric::setBundleState( Bundle::STATE_STARTED );
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT GardenerBundle::bundleStop() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STOPPING) ) return WRET_ERR_WRONG_STATE;

	WRESULT bus_b_disconnected = BusBuilding.disconnect(Bus::ACTION_DEFERRED);
	if ( WFAILED(bus_b_disconnected) )
		SystemError::raise(this, Log, SystemError::WARNING, 0, "bundleStart", "FAILED#%d Disconnecting Bus (wosh.Bus.Building)..", bus_b_disconnected );

	savePrograms();

	this->programs.transactionBeginRead();
	tStrGardenerProgramMap::ConstIterator it;
	for ( it=this->programs.begin(); it!=this->programs.end(); ++it ) {
		if ( it->second == NULL ) continue;
		if ( !it->second->isActive() ) continue;
		it->second->stop();
	 }
	this->programs.transactionEnd();

	WRESULT ret = this->scheduler->stopScheduler(30000);

	BundleGeneric::setBundleState( Bundle::STATE_STOPPED );
	return ret;
 }

////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void GardenerBundle::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;
	if ( !MessageFilter::filterReplica(message, &BusCore) ) return;
	BundleGeneric::busMessage(message, source);
	if ( message.getContent()->isFact() ) {
		const wosh::Fact* fact = message.getContent()->asFact();
		if ( fact->getEvent() == _WeatherInfo_EVENT_WeatherConditionUpdated ) {
			const wosh::xtended::WeatherCondition_Updated* wc_news = message.getContent()->as<wosh::xtended::WeatherCondition_Updated>();
			evaluateWeatherUpdate(wc_news->getWeatherInfo(), wc_news->getSimilarity() );
		 }
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT

bool GardenerBundle::updatingProperty( bool& do_update, const Variant& value_proposed, Property& property_current, const PropertiesProvider* source ) {
	return BundleGeneric::updatingProperty(do_update, value_proposed, property_current, source);
 }

////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////// SCHEDULER EVENT

void GardenerBundle::taskEvent( wosh::xtended::SchedulerTask* task, int64, wosh::xtended::Scheduler* ) {
	if ( task == NULL ) return;
	if ( task->getID() == 0 ) {
		Log(LOG_WARNING, ":taskEvent() Task ID is 0. Removing Task" );
		return;
	 }
	this->programs.transactionBeginRead();
	tStrGardenerProgramMap::ConstIterator it;
	this->programs.transactionBeginRead();
	for ( it=this->programs.begin(); it!=this->programs.end(); ++it ) {
		if ( it->second == NULL ) continue;
		if ( it->second->getID() != task->getID() ) continue;
		if ( it->second->isActive() ) {
			Log(LOG_WARNING, ":taskEvent(%"PRId64") : Program '%s' Already Active!", task->getID(), it->second->getName().c_str() );
		 }
		else {
			Log(LOG_CRITICAL, ":taskEvent(%"PRId64") : Starting Program '%s'", task->getID(), it->second->getName().c_str() );
			it->second->start();
		 }
		this->programs.transactionEnd();
		return;
	 }
	Log(LOG_CRITICAL, ":taskEvent(%"PRId64") : NO linked-Program found. Removing Task", task->getID() );
	this->programs.transactionEnd();
 }

///////////////////////////////////////////////////////////////////////////////////////////// SCHEDULER EVENT
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT GardenerBundle::addProgram_( GardenerProgram* program ) {
	if ( program == NULL ) return WRET_ERR_PARAM;
	Log(LOG_INFO, "addProgram(%s) : Adding Program#%"PRId64"..", program->getName().c_str(), program->getID() );
	program->setListener(this);
	program->setNextStartTime(program->getNextStartTime());
	this->programs.set(program->getName(), program);
	return WRET_OK;
 }

void GardenerBundle::evaluateWeatherUpdate( const wosh::xtended::WeatherInfo* updated_info, double similarity ) {
	if ( updated_info == NULL ) return;
	Log(LOG_INFO, "evaluateWeatherUpdate() : Forwarding news.." );
	tStrGardenerProgramMap::ConstIterator it;
	this->programs.transactionBeginRead();
	for ( it=this->programs.begin(); it!=this->programs.end(); ++it ) {
		if ( it->second == NULL ) continue;
		if ( !it->second->isEnabled() ) continue;
		if ( !it->second->isActive() ) continue;
		it->second->evaluateWeatherUpdate(updated_info, similarity);
	 }
	this->programs.transactionEnd();
 }

WRESULT GardenerBundle::controlProgram( const std::string& target_program, bool activate ) {
	this->programs.transactionBeginRead();
	if ( !this->programs.exists(target_program) ) {
		this->programs.transactionEnd();
		return WRET_ERR_PARAM;
	 }
	Log(LOG_INFO, "controlProgram(%s) : [activate=%d]", target_program.c_str(), activate );
	tStrGardenerProgramMap::ConstIterator it;
	for ( it=this->programs.begin(); it!=this->programs.end(); ++it ) {
		if ( it->second == NULL ) continue;
		if ( !it->second->isEnabled() ) continue;
		if ( target_program != it->first ) {
			if ( it->second->isActive() )
				it->second->stop();
			continue;
		 }
		if ( activate )
			it->second->start();
		else
			it->second->stop();
	 }
	this->programs.transactionEnd();
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT GardenerBundle::loadPrograms() {
	FilePath xmlFile;
	if ( !xmlFile.set( Properties.getValueOf(_Gardener_KEY_DB).toString() ) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "loadPrograms", "FAILED#1 Invalid Archive File [%s]", xmlFile.getPath().c_str() );
		return WRET_ERR_PARAM;
	 }
	if ( !xmlFile.exists() ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "loadPrograms", "FAILED#1 Archive File doesn't exists %s [%s]!", xmlFile.getPath().c_str(), xmlFile.getPathLocal().c_str() );
		return WRET_ERR_PARAM;
	 }
	// may also be a folder of .xml files
	Log(LOG_VERBOSE, ":loadPrograms() File %s ..", xmlFile.getPathLocal().c_str() );

	std::vector<GardenerProgram*> objects;
	WRESULT ret = PersistenceManager::loadObjects( objects, "XML", xmlFile.getPathLocal() );

	if ( WFAILED(ret) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "loadPrograms", "FAILED#%d loading Database %s..", ret, xmlFile.getPathLocal().c_str() );
		return ret;
	 }
	if ( objects.size() == 0 ) {
		Log(LOG_WARNING, ":loadPrograms() : No Items found in database.." );
		return WRET_OK;
	 }

	// register objects
	this->programs.transactionBeginWrite();
	std::vector<GardenerProgram*>::iterator it;
	for ( it=objects.begin(); it!=objects.end(); it++ ) {
		if ( *it == NULL ) continue;
		addProgram_( *it );
	 }
	this->programs.transactionEnd();

	return ret;
 }

WRESULT GardenerBundle::savePrograms() {
	FilePath xmlFile;
	xmlFile.set("$DATABASE/gardener.programs.xml2.bak");
	Log(LOG_VERBOSE, ":savePrograms() File %s ..", xmlFile.getPathLocal().c_str() );
	this->programs.transactionBeginRead();
	WRESULT ret = PersistenceManager::saveObjects( this->programs.getMap(), "XML", xmlFile.getPathLocal() );
	this->programs.transactionEnd();
	if ( WFAILED(ret) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "savePrograms", "FAILED#%d saving Database %s..", ret, xmlFile.getPathLocal().c_str() );
	 }
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void GardenerBundle::onProgramStarted( GardenerProgram* program ) {
	// generate event
 }

void GardenerBundle::onProgramFinished( GardenerProgram* program ) {
	// generate event

 }

void GardenerBundle::onProgramReSchedule( GardenerProgram* program ) {
	if ( program == NULL ) return;
	if ( this->scheduler == NULL ) {
		return;
	 }
	this->scheduler->setupTask(program->getID(), program->getNextStartTime() );
 }

void GardenerBundle::postMessageForZone( Message* message, GardenerProgram* ) {
	if ( message == NULL ) {
		return;
	 }
	signMessage(message);
	BusBuilding.postMessage(message);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS

Response* GardenerBundle::mmDoCtrlProgram( const Request* request ) {
	if ( request == NULL ) return NULL;
	Variant program_name, program_action;
	request->extractArguments(program_name, program_action);
	if ( !program_name.isStringNotEmpty() )
		return request->replyResponse(WRET_ERR_PARAM, "Invalid Param [string, bool]");
	WRESULT ret = controlProgram(program_name.asString(), program_action.toBoolean(true) );
	return request->replyResponse(ret);
 }
/*
Response* GardenerBundle::mmDoScheduleProgram( const Request* request ) {
	if ( request == NULL ) return NULL;
	Variant program_name, program_action;
	request->extractArguments(program_name, program_action);
	if ( !program_name.isStringNotEmpty() )
		return request->replyResponse(WRET_ERR_PARAM, "Invalid Param [string, bool]");
	

	WRESULT ret = controlProgram(program_name.asString(), program_action.toBoolean(true) );
	return request->replyResponse(ret);
 }
*/
///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace services
}; // namespace wosh
